<HTML>
<HEAD>
   <H1>MUSCLE Overview and Beginner's Guide</H1>
   <H4>v5.43 / Jeremy Friesner / Meyer Sound Laboratories Inc (jaf@meyersound.com) 3/29/2011</H4>
   <A HREF="http://www.lcscanada.com/muscle/html/index.html">Click here for DOxygen class API documentation</A>
</HEAD>

<p>
<BODY bgcolor=#ffffff>

<br><br>

<H2>Introduction</H2>

The MUSCLE system is a robust, somewhat scalable, cross-platform client-server solution for dynamic distributed applications for Linux, BSD, Windows, MacOS/X, and other operating systems.  It allows (n) client programs (each of which may be running on a separate computer and/or under a different OS) to communicate with each other in a many-to-many message-passing style.  It employs a central server to which client programs may connect or disconnect at any time  (This design is similar to other client-server systems such as Quake servers, IRC servers, and Napster servers, but more general in application).  In addition to the client-server system, MUSCLE contains classes to support peer-to-peer message streaming connections, as well as some handy miscellaneous utility classes.  As distributed, the server side of the software is ready to compile and run, but to do much with it you'll want to write your own client software.  Example client software can be found in the "test" subdirectory.<p>

This document assumes you are familiar with C++ programming programming.  However it should be understandable even if you aren't.


<H3>Feature List</H3>
<OL>
  <LI> <b>Powerful:</b>  Provides a centralized "message crossbar server" for up to (n) simultaneous client programs to connect to.  (n is limited only by the OS's FD_SET size--typically 1024 or higher).</LI>
  <LI> <b>Easy:</b>  All communication is done over TCP, by sending flattened Message objects through MessageIOGateways.  Under most popular programming environments, it's even easier--see item 8.</LI>
  <LI> <b>Efficient:</b>  Messages sent to the server may be broadcasted to all connected clients, or multicasted intelligently using pattern-matching and/or boolean filtering logic. </LI>
  <LI> <b>Portable:</b>  All code (except for some platform-specific convenience classes in the support folders) uses only standard C++ and BSD socket calls, and should compile and run under any modern OS with minimal changes.  All code has been compiled and tested on 32-bit and 64-bit platforms including Linux, MacOS/X, Windows, and BSD. </LI>
  <LI> <b>Flexible:</b>  Clients may store data (in the form of Messages) in the server's RAM, using a filesystem-like node hierarchy.  Other clients may "subscribe" to this server-side data, and the server will then automatically send them updates to the data as it is changed.  Subscriptions are also specified via wildcarding, for maximum flexibility.  Server-side filtering of results using boolean tests of their content is also supported.</LI>
  <LI> <b>Open:</b>  All source code is licensed under the BSD Open Source License, and is freely distributable and usable for any purpose.  The source code contains many useful classes, including platform-neutral analogs to Be's BMessage, BDataIO, BFlattenable, and BString classes.  In addition, the archive also includes handy double-ended-queue, Hashtable, Reference-counting, and "I/O gateway" classes.</LI>
  <LI> <b>Customizable:</b>  All server-side session handlers are implemented by subclassing a standard interface (AbstractReflectSession) so that they can be easily augmented or replaced with custom logic.  Message serialization and low-level I/O is handled in a similar fashion, making it easy to replace the byte-stream format or transport mechanism with your own. </LI>
  <LI> <b>Convenient:</b>  For selected environments, including Windows, BeOS, Qt, Java, Delphi, and Python, special utility classes are provided to hide the synchronous TCP messaging interface behind an asynchronous send-and-receive-messages API that's easier to deal with.</LI>
</OL>
<p>

<H3>Is this software appropriate for use in my project?</H3>

The space of possible networking applications is large, and MUSCLE may or may not be appropriate for any given networking application.  Here are several questions you should ask yourself when deciding whether or not to use the MUSCLE system or APIs.<p>

<OL><LI><i>Must my application be compatible with pre-existing Internet RFCs or other programs or data formats?</i><p>
If yes, MUSCLE may not be for you.  MUSCLE defines its own byte-stream formats and messaging protocol, and is not generally compatible with other software protocols (such as IRC or FTP).  If your pre-existing protocol follows a "message-stream-over-TCP-stream" design pattern, you can customize MUSCLE (by defining your own subclass of AbstractMessageIOGateway) to make it use your protocol; if not, you're probably better off coding to lower level networking APIs.</LI>
<p>
<LI><i>Is TCP stream communication fast enough for my app?  Do I need to use lower level protocols such as UDP or ICMP?</i><p>
MUSCLE does all of its data transfer by serializing Messages over TCP streams.  If your application is a particularly high-performance one (such as video streaming), MUSCLE may not be able to provide you with the efficiency you need.  In this case, you might use MUSCLE TCP streams for your control data only, and hand-code separate routines for your high-bandwidth/low-latency packets.  I've used this pattern (TCP + UDP) in audio-over-Internet programs before and it works well.<p>
In addition, you should be aware of the CPU and memory overhead added by MUSCLE to your communications.  While MUSCLE has been designed for efficiency, and will not make unreasonable demands on systems that run it, it is necessarily somewhat less efficient that straight byte-stream TCP programming.  Specifically:<p>
   <OL>
     <LI>Its use of Messages means that there will be several dynamic allocations and deallocations, and an extra data copy, for each message sent and received.  (note: ObjectPools are used to minimize the former)</LI>
     <LI>It uses arbitrary-length message queues to avoid ever having to "block" your application's threads.  While this will keep your application reliably responsive to the user, it can potentially use a lot of memory if you are producing messages faster than the network connection can send them.</LI>
     <LI>If you use the MessageTransceiverThread class there will be one extra thread used for each MUSCLE TCP connection.  In addition, MessageTransceiverThread (and the muscled) uses select() to arbitrate data flows, which can be inefficient under the currently released (R5 and earlier) BeOS networking stacks.  (it's more efficient under other operating systems, and should be fine under BONE as well)
   </OL>
  
</LI>
<p>
<LI><i>Should my application use the muscled server, or just point-to-point messaging connections?</i>
<p>
There are two common ways to use the MUSCLE package:  you can have each client connect to a muscled server running on a central server system, and use it to communicate with each other indirectly... or you can have clients connect to each other directly, without using a central server.  Each style of communication is useful in the right context, but it is important to choose the one that best fits what your app is going to do.  Using the muscled in a client/server pattern is great because it solves several problems for you:  it provides a way of communicating with other client computers without first needing to know their host addresses (etc), it gives you intelligent "broadcast" and "multicast" capabilities, and it provides a centralized area to maintain "shared state information" amongst all clients.  On the down side, because all data must travel first to the central server, and from there on to the other client(s), message passing through the server is only half as fast (on average) as a direct connection to another client.  Of course, to get the best of both worlds, you can use a hybrid system:  each client connects to the server, and uses the server to find out the host addresses of the other clients; after that, it can connect to the other clients directly whenever it wants.</LI>
<p>
<LI><i>Which parts of MUSCLE should I use?  Which parts should I extend?  Which parts should I ignore?</i>
<p>
The MUSCLE package consists of dozens of classes, most of which are needed by the MUSCLE server, some of which are needed by MUSCLE clients, and some of which may be useful to you in their own right, as generic utility classes.  For most applications, the standard MUSCLE server will be adequate:  you can just compile it and run it, and concentrate solely on the client side of your app.  For some specialized apps, you may want to make your own "custom" server--you can do this easily by creating your own subclass of AbstractReflectSession.  Of course, if you do this you won't be able to use any of the "general purpose" muscled servers that may be available...
</LI>
</OL>
<p>

<H2>The Multi-Threaded messaging API</H2>

MUSCLE supports a multi-threaded messaging model as well as the single-threaded model (described elsewhere in this document).  In the multi-threaded model, a separate MUSCLE thread is started up to handle networking chores for you.  The advantage of doing it this way is that your GUI will never lock up due to networking activity, since all networking activity happens asynchronously.  Your GUI merely sends commands to the MUSCLE networking thread, and receives Messages back which contain information received from the network.
<p>
The multi-threaded messaging API for MUSCLE is represented mainly by the MessageTransceiverThread class.  This class manages the interaction between you and its internal thread, which does all the networking operations.  If you are using Qt, Windows, BeOS, or AtheOS, you are in luck--there are API-specific subclasses of MessageTransceiverThread included with MUSCLE to make things easier for you.  If not, you can still use MessageTransceiverThread, but you will need to do a little extra work to integrate it with your native threading model (how to do this is not covered here -- email me if you need help with this).
<p>
There are five things that you'll need to do in a typical client:  Set up the thread, connect to the server, send messages, receive messages, and disconnect.  Here's how to do them:

<OL><LI><i>Setting up the MessageTransceiverThread object.</i><p>
First thing you will need to do is create a (AMessageTransceiverThread/BMessageTransceiverThread/QMessageTransceiverThread) object (either on the stack or the heap).  The AMessageTransceiverThread and BMessageTransceiverThread class constructors take a Messenger object; pass in a Messenger that points to the Looper object that will be handling network interactions.  For Qt, it's even easier... just connect() the various signals of the QMessageTransceiverThread object to the various slots of your control object.</LI>

<LI><i>Connecting to the server.</i><p>
Once you have your MessageTransceiverThread object, you can tell it that you want to connect out to the server by calling AddNewConnectSession() on it with the server's hostname and port number.  Then you call StartInternalThread() on it to start the networking thread going.  Both AddNewConnectSession() and StartInternalThread() will return immediately, but when the background TCP thread connects to the server (or fails to do so) it will send an event-message to your target Messenger to notify you (in Qt, it will just emit the appropriate signal).</LI>

<LI><i>Sending messages.</i><p>
To send a message to the server, just call the MessageTransceiverThread's SendMessageToSessions() method.  This method will return immediately, but the message you specify will be placed in an outbound-message queue for sending as soon as possible.  Messages are passed in using a MessageRef reference object, to avoid needless data-copying.  For example:
<p>
<pre>MessageRef newMsg = GetMessageFromPool('HELO');
if (newMsg())
{
   newMsg()->AddString("testing", "please");
   if (myTransceiver.SendMessageToSessions(newMsg) != B_NO_ERROR) printf("Couldn't send message!\n");
}
else printf("Couldn't allocate a message to send... out of memory!\n");</pre><p>
</LI>

<LI><i>Receiving messages</i><p>
If you're using Qt, this is easy--whenever a Message arrives from the server, the MessageReceived() signal will be emitted and your connected object can act on it.  For AtheOS and BeOS, the process is slightly more involved:  Whenever a new Message arrives from the server, a MUSCLE_THREAD_SIGNAL BMessage will be sent to you via the Messenger you specified in the MessageTransceiverThread constructor.  When you receive such a message, your Looper should do something like this:<p>

<pre>MessageRef msg;
uint32 code;
while(myTransceiver.GetNextEventFromInternalThread(code, &msg) >= 0)
{
   switch(code)
   {
      case MTT_EVENT_INCOMING_MESSAGE:
      {
         Message * pMsg = msg();  // Get access to the reference's held Message object.
         HandleMessage(pMsg);     // Do whatever you gotta do
         /* do NOT delete (pMsg).  It will be deleted for you. */
      }
      break;

      case MTT_EVENT_SESSION_CONNECTED:
         printf("Connection to server was successful!\n");
      break;

      case MTT_EVENT_SESSION_DISCONNECTED:
         printf("Disconnected from server, or connection failed!\n");
      break;
    }
};</pre><p>
</LI>

<LI><i>Disconnecting</i><p>
When you've had enough of chatting with the server, you can end your session by calling ShutdownInternalThread() on the MessageTransceiverThread object, and then deleting it.  Or if you wish to reuse the MessageTransceiverThread again, call Reset() on it, and it's ready to use again, as if you had just created it.
<p>
</LI>

</OL>

<H2>The Single-Threaded messaging API (available on all platforms)</H2>
<p>
For code that needs to run on platforms other than Qt/Windows/BeOS/AtheOS (or even for Qt/Windows/BeOS/AtheOS code where you don't want to spawn an extra thread), you can use the single-threaded messaging API, as defined by the DataIO and MessageIOGateway classes.  These classes allow you to decouple your TCP data transfer calls from your message processing calls, and yet still keep the same general message-queue semantics that we know and love.<p>
To create a connection to the MUSCLE server, you would first make a TCP connection using standard BSD sockets calls (see portablereflectclient.cpp for an example of this).  Once you have a connected socket, you would use it to create a TCPSocketDataIO, which you would use to create a MessageIOGateway object:<p><pre>
MessageIOGateway gw;  // create the gateway
gw.SetDataIO(DataIORef(new TCPSocketDataIO(mysocketfd, false)));  // tell the gateway to use our TCP socket
</pre>
<p>
This gateway allows you to enqueue outgoing Message or dequeue incoming Messages at any time by calling AddOutgoingMessage() or GetNextIncomingMessage(), respectively.  These methods are guaranteed never to block.  Like the MessageTransceiverThread, the MessageIOGateway uses MessageRef objects to handle the freeing of Messages when they are no longer in use.<p>
<p>
To actually send and receive TCP data, you need to call DoOutput() and DoInput(), respectively.  These methods will send/receive as many bytes of TCP data as they can (without blocking), and then return B_NO_ERROR (unless the connection has been cut, in which case they will return B_ERROR).  Because these methods never block (unless your TCPSocketDataIO is set to blocking I/O mode, which in general it shouldn't be), you will need to employ select() or some other method to keep your event loop from using 100% CPU time while waiting to send or receive data.  Here is an example event loop that does this:<p>

<pre>ConstSocketRef connectionSocket = Connect("servername.serverdomain.com", 2960);  // get a fresh TCP socket connection
if (connectionSocket())
{
   MessageIOGateway gw;
   gw.SetDataIO(DataIORef(new TCPSocketDataIO(connectionSocket, false)));
   bool keepGoing = true;
   struct fd_set readSet, writeSet;
   QueueGatewayMessageReceiver inputQueue;
   while(keepGoing)
   {
      int socketFD = connectionSocket.GetFileDescriptor();

      FD_ZERO(&readSet);
      FD_SET(socketFD, &readSet);

      FD_ZERO(&writeSet);
      if (gw.HasBytesToOutput()) FD_SET(socketFD, &writeSet);

      if (select(socketFD+1, &readSet, &writeSet, NULL, NULL) &lt; 0)
      {
         perror("select() failed");
         keepGoing = false;
      }

      bool readyToWrite = FD_ISSET(socketFD, &writeSet);
      bool readyToRead  = FD_ISSET(socketFD, &readSet);

      /* Do as much TCP I/O as possible without blocking */
      bool writeError = ((readyToWrite)&&(gw.DoOutput()         &lt; 0));
      bool readError  = ((readyToRead)&&(gw.DoInput(inputQueue) &lt; 0));
      if ((readError)||(writeError)) keepGoing = false;

      /* handle any received messages */
      MessageRef msg;
      while(inputQueue.RemoveHead(msg) == B_NO_ERROR)
      {
         printf("Received incoming TCP Message:\n");
         if (msg()) msg()->PrintToStream();  // handle message here
      }
   }
   printf("Connection was closed!\n");
}
else printf("Couldn't connect!\n");</pre><p>

Alternatively, you can set the blocking-I/O parameter in the TCPSocketDataIO object to true, and use blocking I/O instead.  If you do that, then you don't have to deal with the complexities of select()... but then it becomes difficult to coordinate sending and receiving at the same time (i.e. how can you call DoOutput() if you are blocked waiting for data in DoInput()?)
<p>

<H2>Message semantics for client/server connections</H2>
<p>
Regardless of whether you are sending and receiving messages with a MessageTransceiverThread with direct calls to a IOGateway, the result looks the same to the program at the other end of the TCP connection:  It always sees a just a sequence of flattened Message objects.  How that program acts on those messages is of course up to it.  However, the server included in this archive does have some minimal standard semantics that govern how it handles the messages it receives.  The following sections describe those semantics.
<p>
<H3>DumbReflectSession semantics</H3>
<p>
If you are connected to a MUSCLE server that was compiled to use the DumbReflectSession class to handle its connections, then the semantics are extremely simple:  Any Message you send to the server will be sent on, verbatim, to every other connected client.  (Sort of a high-level version of Ethernet broadcast packets).  This may be useful in some situations, but for applications where bandwidth is an issue you'll probably want to use the "regular" server with StorageReflectSession semantics.
<p>
<H3>StorageReflectSession semantics</H3>
<p>
The StorageReflectSession-based server (a.k.a. "muscled") is much more powerful than the DumbReflectSession server, for two reasons:  First, it makes intelligent decisions about how to route client messages, so that your messages only go to the clients you specify.  The second reason is because this server allows you to store messages (semi-permanently; they are retained for as long as you remain connected) in the server's RAM, where other clients can access them without having to communicate with you directly.  If you imagine a situation where the server is running on 100Mbps Ethernet, and the clients are connecting through 28.8 modems, then you can see how this can be useful.
<p>
The StorageReflectSession server maintains a single tree data structure very much like the filesystem of your average desktop computer.  Although this data structure exists only in memory (nothing is ever written to the server's disk), it shares many things in common with a multi-user file system.  Each node in the tree has an ASCII label that uniquely identifies it from its siblings, and also contains a single Message object, which client machines may get or set (with certain restrictions).  The root node of the tree contains no data, and is always present.  Nodes underneath the root, on the other hand, may appear and dissappear as clients connect and disconnect.  The first level of nodes beneath the root are automatically created whenever a client connects to the server, and are named after the host IP address of the client machine that connected.  (For example, "192.168.0.150").  The second level of nodes are also automatically created, and these nodes are given unique names that the server makes up arbitrarily.  (This second level is necessary to disambiguate multiple connections coming from the same host machine)  The number of level 2 nodes in the tree is always the same as the number of currently active connections ("sessions") on the server.
<p>
<pre>       ___________'/'_________               (level 0 -- "root")
      |                      |
  192.168.0.150         132.239.50.13        (level 1 -- host IP addresses)
   |         |               |
3217617   3217618         1829023            (level 2 -- unique session IDs)
   |                         |
SomeData                  MoreData           (level 3 -- user data nodes)
                          |      |
                       RedFish BlueFish      (level 4)</pre><p>

Levels 1 and 2 of the tree reflect two simultaneous sessions connected from 192.168.0.150, and one connection from 132.239.50.13.  In levels 3 and 4, we can see that the sessions have created some nodes of their own.  These "user-created" nodes can be named anything you want, although no two siblings can have the same name.  Each client may create data nodes only underneath its own "home directory" node in level 2--you aren't allowed to write into the "home directories" of other sessions.  However, any client may read the contents of any node in the system.<p>
As in any good filesystem (e.g. UNIX's), nodes can be identified uniquely by a node-path.  A node-path is simply the concatenation of all node names from the root to the node, separated by '/' characters.  So, the tree in the above example contains the following node-paths:

<pre>/
/192.168.0.150
/192.168.0.150/3217617
/192.168.0.150/3217617/SomeData
/192.168.0.150/3217618
/132.239.50.13
/132.239.50.13/1829023
/132.239.50.13/1829023/MoreData
/132.239.50.13/1829023/MoreData/RedFish
/132.239.50.13/1829023/MoreData/BlueFish</pre><p>

<H4>Creating or modifying nodes in your subtree (a.k.a. uploading data)</H4><p>

One thing most clients will want to do is create one or more new nodes in their subtree on the server.  Since each node contains a Message, creating a node is the same thing as uploading data to the server.  To do this, you send the server a PR_COMMAND_SETDATA message.  A single PR_COMMAND_SETDATA message can set any number of new nodes.  For each node you wish to set, simply AddMessage() the value you wish to set it to, with a field name equal to the path of the node relative to your "home directory".  For example, here's what the client from 132.239.50.13 could have done to create the MoreData, RedFish, and BlueFish nodes under his home directory:<p>

<pre>Message redFishMessage('RedF');   // these messages could contain data
Message blueFishMessage('BluF');  // you wish to upload to the server

MessageRef msg = GetMessageFromPool(PR_COMMAND_SETDATA);
if (msg())
{
   msg()->AddMessage("MoreData/RedFish", redFishMessage);
   msg()->AddMessage("MoreData/BlueFish", blueFishMessage);
   myMessageTranceiver->SendMessageToSessions(msg);
}
else printf("Out of memory?!\n");
</pre><p>

Note that the "MoreData" node did not need to be explicitely created in this message; the server will see that it doesn't exist and create it before adding RedFish and BlueFish to the tree.  (Nodes created in this way have empty Messages associated with them).  If 132.239.50.13 later wants to change the data in any of these nodes, he can just send another PR_COMMAND_SETDATA message with the same field names, but different messages.<p>

<H4>Accessing nodes in the tree (a.k.a. downloading data)</H4><p>

If you want to find out the current state of one or more nodes on the server, you should send a PR_COMMAND_GETDATA message.  In this PR_COMMAND_GETDATA message, you should add one or more strings to the PR_NAME_KEYS field.  Each of these strings may specify the full path-name of a node in the tree that you are interested in.  For example:

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_GETDATA);
if (msg())
{
   msg()->AddString(PR_NAME_KEYS, "/192.168.0.150/3217617/SomeData");
   msg()->AddString(PR_NAME_KEYS, "/132.239.50.13/1829023/MoreData/RedFish");
   msg()->AddString(PR_NAME_KEYS, "/132.239.50.13/1829023");
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

Soon after you sent this message, the server would respond with a PR_RESULT_DATAITEMS message.  This message would contain the values you asked for.  Each value is stored in a separate message field, with the field's name being the full node-path of the node, and the field's value being the Message that was stored with that node on the server.  So for the above request, the result would be:

<pre>Message: what = PR_RESULT_DATAITEMS  numFields = 3
  field 0: name = "/192.168.0.150/3217617/SomeData" value = (a Message)
  field 1: name = "/132.239.50.13/1829023/MoreData/RedFish" value = (a Message)
  field 2: name = "/132.239.50.13/1829023" value = (an empty Message)</pre><p>

<H5>Pattern matching in node paths</H5>

Of course, not all the nodes you specified may actually exist on the server; if the server cannot find a node that you requested it simply won't add it to the PR_RESULT_DATAITEMS message it sends you.  Thus it's possible to get back an empty PR_RESULT_DATAITEMS message if you're unlucky.<p>

The above method of retrieving data is okay as far as it goes, but it only works if you know in advance the node-path(s) of the data you want.  But in the real world, you won't usually know e.g. the host addresses of other connected clients.  Fortunately, the MUSCLE server understands wildcard patterns in the node-paths you send it.  Wildcarding allows you to specify a pattern to watch for rather than a particular unique string.  A detailed discussion of pattern matching is outside the scope of this document, but if you've used any UNIX-style shell much at all you probably have a good idea how they work.  For example, say we wanted to know the host address of every machine connected to the server:<p>

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_GETDATA);
if (msg())
{
   msg()->AddString(PR_NAME_KEYS, "/*");
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

The "/*" pattern in the PR_NAME_KEYS field above matches both "/192.168.0.150" and "/132.239.50.13" in the tree, so we would get back the following:

<pre>Message: what = PR_RESULT_DATAITEMS  numFields = 2
  field 0: name = "/192.168.0.150" value = (an empty Message)
  field 1: name = "/132.239.50.13" value = (an empty Message)</pre><p>

Or, perhaps we want to know about every node in every session's home directory that starts with the letters "Som".  Then we could do:

<pre>msg()->AddString(PR_NAME_KEYS, "/*/*/Som*");</pre><p>

And so on.  And of course, you are still able to add multiple PR_NAME_KEYS values to a single PR_COMMAND_GETDATA message;  the PR_RESULT_DATAITEMS message you get back will contain data for any node that matches at least one of your wildcard patterns.<p>

One more detail:  Since patterns that start with "/*/*" turn out to be used a lot, they can be made implicit in your path requests.  Specifically, any PR_NAME_KEYS value that does not start with a leading '/' character is taken to have an implicit '/*/*/' prefix.  So doing

<pre>msg()->AddString(PR_NAME_KEYS, "Gopher");</pre><p>
is semantically equivalent to doing

<pre>msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");</pre><p>

<H4>Deleting nodes in your subtree</H4>

To remove nodes in your subtree, send a PR_COMMAND_REMOVEDATA message to the server.  Add to this message one or more node-paths (relative your session directory) indicating the node(s) to remove.  These paths may have wildcards in them.  For example, if 132.239.50.13 wanted to remove all nodes from his subtree, he could do this:<p>

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_REMOVEDATA);
if (msg())
{
   msg()->AddString(PR_NAME_KEYS, "MoreData/RedFish");
   msg()->AddString(PR_NAME_KEYS, "MoreData/BlueFish");
   msg()->AddString(PR_NAME_KEYS, "MoreData");
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

or this:

<pre>msg()->AddString(PR_NAME_KEYS, "MoreData");  /* Removing a node implicitely removes its children */</pre><p>

or even just this:

<pre>msg()->AddString(PR_NAME_KEYS, "*");  /* wildcarding */</pre><p>

You can only remove nodes within your own subtree.  You can add as many PR_NAME_KEYS strings to your PR_COMMAND_REMOVEDATA message as you wish.

<H4>Sending messages to other clients</H4>

Any message you send to the server whose 'what' value is not one of the PR_COMMAND_* constants is considered by the server to be a message meant to be forwarded to the other clients.  But which ones?  Again, the issue is decided by using pattern matching on node-paths.  The server will examine your message for a PR_NAME_KEYS string field.  If it finds one (or more) strings in this field, it will use these strings as node-paths; any other client whose has one or more nodes that match your node-path expressions will receive a copy of your message.  For example:

<pre>MessageRef msg = GetMessageFromPool('HELO');
if (msg())
{
   msg()->AddString(PR_NAME_KEYS, "/192.168.0.150/*");
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

would cause your 'HELO' message to be sent to all sessions connecting from 192.168.0.150.  Or, more interestingly:

<pre>msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");</pre><p>

Would cause your message to be sent to all sessions who have a node named "Gopher" in their home directory.  This is very handy because it allows sessions to "advertise" for which types of message they want to receive:  In the above example, everyone who was interested in your 'HELO' messages could signify that by putting a node named "Gopher" in their directory.<p>

Other examples of ways to address your messages:

<pre>msg()->AddString(PR_NAME_KEYS, "/*/*/J*")</pre><p>

Will send your message to all clients who have a node in their home directory whose name begins with the letter 'J'.<p>

<pre>msg()->AddString(PR_NAME_KEYS, "/*/*/J*/a*/F*")</pre><p>

This (contrived) example would send your message only to clients who have something like "Jeremy/allen/Friesner" present...<p>

<pre>msg()->AddString(PR_NAME_KEYS, "Gopher");</pre><p>

This is equivalent to the "/*/*/Gopher" example used above;  if no leading slash is present, then the "/*/*/" prefix is considered to be implied.<p>

<pre>msg()->AddString(PR_NAME_KEYS, "Gopher");
msg()->AddString(PR_NAME_KEYS, "Bunny");</pre><p>

This message will go to clients who have node named either "Gopher" or "Bunny" in their home directory.  Clients who have both "Gopher" AND "Bunny" will still only get one copy of this message.<p>

If your message does not have a PR_NAME_KEYS field, the server will check your client's parameter set for a string parameter named PR_NAME_KEYS.  If this parameter is found, it will be used as a "default" setting for PR_NAME_KEYS.  If a PR_NAME_KEYS parameter setting does not exist either, then the server will resort to its "dumb" behavior:  broadcasting your message to all connected clients.<p>

<H4>Subscriptions (a.k.a. automatic change notification triggers)</H4>

Theoretically, getting and setting data nodes is all that is necessary for meaningful client-to-client data transfer to take place.  In real life, however, it's not terribly useful on its own.  After all, what good is it to download the data from a bunch of nodes if that data might be changed 50 milliseconds after it was sent to you?  You'd end up having to issue another PR_COMMAND_GETDATA message every few seconds just to make sure you had the latest data.  Now imagine fifty simultaneously connected clients, all doing that at once.  No, that would never do.<p>

To deal with this problem, the StorageReflection Server allows your client to set "subscriptions".  Each subscription is nothing more than the node-path of one or more nodes that your client is interested in.  The path format and semantics of a subscription request are exactly the same as those in a PR_COMMAND_GETDATA message, but the way you compose them is quite different.  Here is an example:

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
if (msg())
{
   msg()->AddBool("SUBSCRIBE:/*/*", true);
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

The above is a request to be notified whenever the state of a node whose path matches "/*/*" changes (which is actually the same as being notified whenever another session connects or disconnects--very handy for some applications).  Note that the subscription path is part of the field's <i>name</i>, not the field's value.  Note also that the field has been added as a boolean.  That actually doesn't matter; you can add your subscribe request as any type of data you wish--the value won't even be looked at, it's only the field's name that is important.<p>

As soon as your PR_COMMAND_SETPARAMETERS message is received by the server, it will send back a PR_RESULT_DATAITEMS message containing values for all the nodes that matched your subscription path(s).  In this respect, your subscription acts similarly to a PR_COMMAND_GETDATA message.  But the difference is that the server keeps your subscription strings "on file", and afterwards, every time a node is created, changed, or deleted--and its node-path matches at least one of your subscription paths, the server will automatically send you another PR_COMMAND_DATAITEMS message containing the message(s) that have changed, and their newest values.  Note that each PR_COMMAND_DATAITEMS message may have more than one changed node in it at a time (i.e. if someone else changes several nodes at a time).<p>

When the server wishes to notify you that a node matching one of your subscription paths has been deleted, it will do so by adding the node-path of the deceased node to the PR_NAME_REMOVED_DATAITEMS field of the PR_RESULT_DATAITEMS message it sends you.  Again, there may be more than one PR_NAME_REMOVED_DATAITEMS value in a single PR_RESULT_DATAITEMS message.

<H4>Cancelling Subscriptions</H4>

Cancelling a subscription is just the same as removing any other parameter from your parameter set; indeed subscriptions are just parameters whose names start with the magic prefix "SUBSCRIBE:".  As such, see the next section on setting and removing parameters for how to do this.  (One caveat:  since parameters to remove are specified with pattern matching, you may need to escape any wildcard characters in your SUBSCRIBE: string to avoid removing additional parameters that you didn't intend to.  There is a utility function in StringMatcher.h that you can use to do this if you like)

<H4>Setting parameters</H4>

In addition to data-node storage, each client holds a set of name-value pairs called its <i>parameter set</i>.  These parameters are used by the session to control certain session-specific policies.  The names in this set may be any ASCII string (although only certain names are actually paid attention to by the server), and the values may be of any type that is allowed as a field in a Message.  To set or modify a parameter for your session, just send a PR_COMMAND_SETPARAMETERS message to the server with the names and values included.  For example:

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
if (msg())
{
   msg()->AddBool(PR_NAME_REFLECT_TO_SELF, true);  // enable wildcard matching on my own subdirectory
   msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");  // set default message forwarding pattern
   msg()->AddBool("SUBSCRIBE:/132.239.50.*", true);       // add a subscription to nodes matching "/132.239.50.*"
   msg()->AddBool("SUBSCRIBE:*", true);            // add a subscription to nodes matching "/*/*/*"
   msg()->AddInt32("Glorp", 666);                  // other parameters like this will be ignored
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

The fields included in your message will replace any like-named fields already existing in the parameter set.  Any fields in the existing parameter set that <i>aren't</i> specified in your message will be left unaltered.<p>

<H4>Getting the current parameter set</H4>

If you wish to know the exact parameter set that your session is currently operating under, send a PR_COMMAND_GETPARAMETERS message to the server.  This message need not contain any fields at all; only the 'what' code is looked at.  When the server receives a PR_COMMAND_GETPARAMETERS message, it will respond by sending you back a PR_RESULT_PARAMETERS message that contains all the name->value pairs in your parameter set as fields.<p>

<H4>Removing parameters</H4>

To remove one or more parameters, send a PR_COMMAND_REMOVEPARAMETERS message.  This message should contain a PR_NAME_KEYS field containing one or more strings that indicate which parameters to remove.  For example:

<pre>MessageRef msg = GetMessageFromPool(PR_COMMAND_REMOVEPARAMETERS);
if (msg())
{
   msg()->AddString(PR_NAME_KEYS, PR_NAME_REFLECT_TO_SELF);  // disable wildcard matching on my own subdirectory
   msg()->AddString(PR_NAME_KEYS, "SUBSCRIBE:*");            // removes ALL subscriptions (compare with "SUBSCRIBE:\*" which would only remove one)
   myMessageTranceiver->SendMessageToSessions(msg);
}</pre><p>

<H4>Recognized parameter names</H4>

Currently there are only a few parameters that whose values are acted upon by the StorageReflect Server.  These are:

<OL>
  <LI>PR_NAME_KEYS<br>
      If present and set to a string value, this value is used as the default pattern to match for determining which sessions user messages (i.e. messages whose 'what' code is not one of the PR_COMMAND_* constants) should be routed to.  See the section on "Sending Messages to Other Clients" for details.  Defaults to unset (which is equivalent to "/*/*").
  </LI>
  <LI>PR_NAME_FILTERS<br>
      If present, and a PR_NAME_KEYS parameter is present as well, this value can be used to specify a QueryFilter object that will refine the routing logic specified in the PR_NAME_KEYS parameter.  See the section on QueryFilter objects for details.
  </LI>
  <LI>PR_NAME_REFLECT_TO_SELF<br>
      If present, all wild-card pattern matches will include your own session's nodes in their result sets.  If not present (the default), only nodes from other sessions will be returned to your client.  This field may be of any type or value; only its existence/non-existence is looked at.
  </LI>
  <LI>PR_NAME_MAX_UPDATE_MESSAGE_ITEMS<br>
      This parameter, which should be of type int32, can be used to specify the maximum number of updates that should be sent back to the client in a single PR_RESULT_DATAITEMS Message.  You can use this to specify the "granularity" of your update notifications.  Default value for this parameter is 50 updates per Message.
  </LI>
  <LI>PR_NAME_PRIVILEGE_BITS<br>
      This is a read-only parameter, and the int32 value of this parameter is a bit-chord indicating what special privileges (if any) your client has been given by the server.  Currently defined privilege bits are PR_PRIVILEGE_KICK, PR_PRIVILEGE_ADDBANS, and PR_PRIVILEGE_REMOVEBANS.
  </LI>
  <LI>PR_NAME_REPLY_ENCODING<br>
      This int32 parameter can be used to control the way Messages going from the server back to your client are encoded.  The default encoding (used if this parameter is not present) is MUSCLE_MESSAGE_ENCODING_DEFAULT, which is a vanilla, uncompressed format.  Other currently supported encodings are MUSCLE_MESSGE_ENCODING_ZLIB_1 through MUSCLE_MESSAGE_ENCODING_ZLIB_9, which enable varying levels of transparent data compression on the data as it is sent back to your.  See iogateway/MessageIOGateway.h for further details.
  </LI>
  <LI>"SUBSCRIBE:..."<br>
      Any string parameter whose name starts with the prefix "SUBSCRIBE:" is treated as a subscription request.  See the section on Subscriptions for details.
  </LI>
  <LI>PR_NAME_DISABLE_SUBSCRIPTIONS<br>
      If this parameter is present (it doesn't matter what its value is) then subscription-updates will be disabled to your client.  To re-enable them send a PR_COMMAND_REMOVEPARAMETERS Message removing this parameter again. 
  </LI>
  <LI>PR_NAME_SUBSCRIBE_QUIETLY<br>
      This isn't actually a parameter itself, but if added to the same PR_COMMAND_SETPARAMETERS message as one or more "SUBSCRIBE:" entries, it will suppress the initial notification of the new subscription data.  That is to say, if you add an item with this name to your message, you won't receive the values of the nodes you subscribed to until the next time they are changed.  (remember that by default you would receive the "current' values of the nodes immediately) 
  </LI>
</OL>

<H2>(Optional) Indexed Nodes and Orderly Children </H2>

New for v1.40 of muscled is support for "indexed" nodes.  An indexed node is the same as any other node,
except that it maintains an ordered index of some or all of its children.  In most cases, this index is 
unecessary, so by default no indexing is done.  There are some cases where the ordering of child nodes 
is important, however--for example, if you are advertising to other clients a list of instructions that 
must be executed in order, and the list too large to upload an entire new list every time the list changes.
<p>
To enable indexing for a node, simply add child nodes to it using PR_COMMAND_INSERTORDEREDDATA
instead of the usual PR_COMMAND_SETDATA messages.  In a PR_COMMAND_INSERTORDEREDDATA message, you
specify the parent node that the new child node is to be added to, and the data/Message the new 
child node will contain--but muscled will be the one to assign the new child node an (algorithmically
generated) name.  Generated names are guaranteed to start with a capital 'I'.  Muscled will add the 
child node in front of a previously added indexed child whose name you specify, or will add it to the 
end of the index if you specify sibling name that is not found in the index.
<p>
Here is an example PR_COMMAND_INSERTORDEREDDATA message that will add an indexed child node to the
node "myNode":
<pre>
Message imsg(PR_COMMAND_INSERTDATA);
imsg.AddString(PR_NAME_KEYS, "myNode");     // specify node(s) to add insert the child node(s) under
Message childData('Chld');   // any data you want to store can be placed in this message....
imsg.AddMessage("I4", childData);    // add the new node before I4, if I4 exists.  Else append to the end.
</pre><p>
If myNode already contains an indexed child with the name I4, the new node will be inserted into
the index just before I4.  If I4 is not found in the index, the new node will be appended to the
end of the index.  If you want to be sure that your new child is always added to the end of the
index, you can just AddMessage() using a field name that doesn't start with a capital I.
<p>
You are allowed to specify more than one parent node in PR_NAME_KEYS (either via wildcarding,
or via multiple PR_NAME_KEYS values)--this will cause the same child nodes to be added to all matching
nodes.  You are also allowed to specify multiple child messages to add in a single INSERTORDEREDDATA message
(either by adding sub-messages under several different field names, or by adding multiple 
sub-messages under a single field name).
<p>
When a node contains an index (i.e. when it has at least one child under it that was added via
PR_COMMAND_INSERTORDERREDDATA) any clients that are subscribed to that node will receive
PR_RESULT_INDEXUPDATED messages when the index changes.  These messages allow the subscribed
clients to update their local copy of the index incrementally.  Each PR_RESULT_INDEXUPDATED
message will contain one or more string fields.  Each string field's name will be the fully
qualified path of the indexed node whose index has been changed.  Each string/value in a given string
field represents a single operation on the index.  An example message might look like this:

<pre>
Message:  this=0x800c32f8, what='!Pr4' (558920244/0x21507234), entryCount=1, flatSize=79
  Entry: Name=[/spork/0/hello], CountItems()=4, TypeCode()='CSTR' (1129534546) flatSize=40
    0. [c]
    1. [i0:I0]
    2. [i1:I1]
    3. [r1:I1]     
</pre><p>

The first letter of each string is an opcode, one of the INDEX_OP_* constants defined in
StorageReflectConstants.h.  Here we see that the first instruction has a 'c', or
INDEX_OP_CLEARED, indicating that the index was cleared.  The next instruction, i0:I0 starts
with a INDEX_OP_ENTRYINSERTED, and indicates that a child node named I0 was inserted at 
index 0.  After that, a child node named I1 was inserted at index 1.  Lastly, the
INDEX_OP_ENTRYREMOVED opcode ('r') indicates that the node at index 1 (I1) was then removed
from the list.  By parsing these instructions, the client can update its own local index
to follow that of the server.
<p>
Note that the index only contains node names and ordering information; the actual node data is kept in the child
nodes, in the normal fashion.  So most clients will want to subscribe to both the indexed parent
node, and its children, in order to display the data that the index refers to.
<p>
An indexed node will also send the contents of its index (in the form of a PR_RESULT_INDEXUPDATED
message with a INDEX_OP_CLEARED op code, followed by one or more INDEX_OP_ENTRYINSERTED opcodes)
to any client that requests it via the PR_COMMAND_GETDATA command.  This message is sent in
addition to the regular PR_RESULT_DATAITEMS message.
<p>
To remove a node entry from the index, simply remove the delete the child node in the normal
fashion, using a PR_COMMAND_REMOVEDATA message.  You can, of course, update a child node's data
using a PR_COMMAND_SETDATA message, without affecting its place in the index.
<p>
One last note is that index data is always sent to all clients that ask for it; it is even
sent to the client who created/owns the indexed node.  That is to say, the PR_NAME_REFLECT_TO_SELF
attribute may be considered always set as far as index data is concerned.  This is because the 
index is created on the server side, and so not even the client-side initiator of the index 
creation can be exactly sure of the index's state.  It's best for clients not to make assumptions
about the contents of the index, and update their local indices based solely on the 
PR_RESULT_INDEXUPDATED messages they receive from the server.

<H2>(Optional) QueryFilters</H2>

New for MUSCLE v2.40 is support for QueryFilter objects.  QueryFilter objects offer you a way
to do content-based server-side filtering of subscription sets and other operations.  Whereas
before you could specify a set of nodes based solely on their node-path, you can now combine
the wildcarded node-path logic with formulas based on the contents of node's data Message as well.
For example, you can now specify a subscription on "all nodes that match /*/*/* that have
an int32 field named 'joe' whose value is less than 5".  Different types of QueryFilter subclass
exist for various purposes, and they can be combined using AND, OR, NOT, and XOR operations to
make fairly sophisticated filters.
<p>
The supported QueryFilter classes are defined in reges/QueryFilter.h.  Here is a slightly abridged 
reprinting of their shared base-class interface:

<pre>
class QueryFilter : public RefCountable
{
public:
   virtual status_t SaveToArchive(Message & archive) const;
   virtual status_t SetFromArchive(const Message & archive);

   virtual bool Matches(const Message & msg) const = 0;
};
</pre>

Of the three method shown above, Matches() is the one that embodies
a QueryFilter's primary purpose:  To determine, given a Message object, whether that
Message matches the filter, or not.  In our example above, we would use a QueryFilter
object whose Matches() method returns true only for Messages with an int32 field named "joe"
whose value is less than 5.

The other two methods are also important -- they are the mechanism by which a C++ QueryFilter
object can travel from your client to the server.  The SaveToArchive() method's job is to save
the QueryFilter's settings into the given Message, and the SetFromArchive() method's job is
to update the QueryFilter's settings to match those previously stored in the Message.
To use QueryFilters, your client will create the QueryFilter object it wants the server
to use, then SaveToArchive() that object into a Message, and send the Message to the server.
When the server gets the Message, it will use it to reconstruct an identical QueryFilter object,
which it can then use to filter Messages for you.

Here is some example code your client might use to set up the aforementioned filtered subscription 
query to the server:

<pre>
Int32QueryFilter filter("joe", Int32QueryFilter::OP_LESS_THAN, 5);
MessageRef filterMsg = GetMessageFromPool();
filter.SaveToArchive(*filterMsg());

MessageRef msgRef = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
msgRef()->AddMessage("SUBSCRIBE:/*/*/*", filterMsg);      
myMtt->SendMessageToSessions(msgRef);
</pre>

As you can see, we set up the subscription in the normal way, except that this
time the SUBSCRIBE: parameter's value is a Message that represents the QueryFilter.
This tells the server to restrict the query ("/*/*/*") further, so that only
nodes whose Messages also meet the "joe &lt; 5" criteria are considered to be matches.

For PR_COMMAND_GETDATA, PR_COMMAND_REMOVEDATA, and other Messages where a node-path
pattern is specified in the PR_NAME_KEYS field, the procedure is slightly different.
For those Messages, you can now add a PR_NAME_FILTERS field that contains Messages
that are archived QueryFilter objects.  For example, say you wanted to remove
all nodes in your "home folder" whose node-names start with the letter Z, and 
whose Message's what-code is 666.  You could do that like this:

<pre>
WhatCodeQueryFilter filter(666);
MessageRef filterMsg = GetMessageFromPool();
filter.SaveToArchive(*filterMsg());

MessageRef msgRef = GetMessageFromPool(PR_COMMAND_REMOVEDATA);
msgRef()->AddString(PR_NAME_KEYS, "Z*");
msgRef()->AddMessage(PR_NAME_FILTERS, filterMsg);
myMtt->SendMessageToSessions(msgRef);
</pre>

There is a fairly large number of different QueryFilter subclasses to choose from.
Here is a brief list of the various available types, and what they do:

<OL>
 <LI>WhatCodeQueryFilter<br>
       This filter matches only Messages whose 'what' code is the specified value,
       or within a specified range of values.
 </LI>
 <LI>ValueExistsQueryFilter<br>
       This filter matches only Messages that contain the specified field name.
       You can also optionally specify what data type that field must be of.
 </LI>
 <LI>BoolQueryFilter, Int8QueryFilter, Int16QueryFilter, Int32QueryFilter,
     Int64QueryFilter, FloatQueryFilter, DoubleQueryFilter, PointQueryFilter,
     and RectQueryFilter<br>
       These filters are all quite similar in that they let you specify a field name
       and a value of the given type, and an operator to use to compare the 
       Message's value in that field with your specified value.  Available operators
       are:  OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN, OP_LESS_THAN_OR_EQUAL_TO,
       OP_GREATER_THAN_OR_EQUAL_TO, and OP_NOT_EQUAL_TO.
 </LI>
 <LI>StringQueryFilter<br>
       This filter looks for a string field inside the Message, and matches only
       if the found string value passes the test specified by the StringQueryFilter.
       You specify an operator and a second value for the operator to take.
       Available string operators are OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN,
       OP_LESS_THAN_OR_EQUAL_TO, OP_GREATER_THAN_OR_EQUAL_TO, OP_NOT_EQUAL_TO,
       OP_STARTS_WITH, OP_ENDS_WITH, OP_CONTAINS, OP_START_OF, OP_END_OF,
       OP_SUBSTRING_OF, OP_EQUAL_TO_IGNORECASE, OP_LESS_THAN_IGNORECASE,
       OP_GREATER_THAN_IGNORECASE, OP_LESS_THAN_OR_EQUAL_TO_IGNORECASE,
       OP_GREATER_THAN_OR_EQUAL_TO_IGNORECASE, OP_NOT_EQUAL_TO_IGNORECASE,
       OP_STARTS_WITH_IGNORECASE, OP_ENDS_WITH_IGNORECASE, OP_CONTAINS_IGNORECASE,
       OP_START_OF_IGNORECASE, OP_END_OF_IGNORECASE, OP_SUBSTRING_OF_IGNORECASE,
       OP_SIMPLE_WILDCARD_MATCH, and OP_REGULAR_EXPRESSION_MATCH.
 <LI>RawDataQueryFilter<br>
       This filter is similar to the StringQueryFilter, except that it operates 
       on raw byte sequences instead.  Since any data item is ultimately a sequence of
       bytes, that means that this filter can be used to match against data of
       any type (although it is probably most useful in conjunction with B_RAW_TYPE
       or flattened-user-object types).  Available operaters for this filter are:
       OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN, OP_LESS_THAN_OR_EQUAL_TO,
       OP_GREATER_THAN_OR_EQUAL_TO, OP_NOT_EQUAL_TO, OP_STARTS_WITH, OP_ENDS_WITH,
       OP_CONTAINS, OP_START_OF, OP_END_OF, OP_SUBSET_OF
 <LI>MessageQueryFilter<br>
       This filter lets you "drill down" into sub-Messages in a Message's content,
       if you wish to.  You can specify a field name and a "child filter", and
       if the MessageQueryFilter finds a Message field inside the node's data Message
       with the given field name, it will call the child QueryFilter on the
       found Message and return the result.
 <LI>AndOrQueryFilter<br>
       This filter is a "meta-filter", in that you can add one or more child
       QueryFilters to it, and it will combine their operations together to make
       a more complex operation.  Its function is this:  It will match the given
       Message if at least (n) of its child filters match the Message.  You get
       to specify what (n) is.  The two most common uses for this filter are OR
       (set n=1, and this filter will match if any one of its children match),
       and AND (set n equal to the number of child filters, and this filter will
       match only if all of its children match).  
 </LI>
 <LI>NandNotQueryFilter<br>
       This filter is another "meta-filter", and is useful primarily for combining
       child QueryFilters together with NAND or NOT operations.  Its function is
       this:  It will match the given Message if no more than (n) of its children
       match the Message.  So, to get a NOT filter, you could add a single child
       to this filter and set (n=0).  To get a NAND filter, you could add two
       children and set (n=1).  And so on.
 </LI>
 <LI>XorQueryFilter<br>
       This meta-filter is quite simple:  it will return true if and only if
       an odd number of its children match the provided Message.  It is mainly
       useful for XOR operations.
 </LI>
</OL>

As you can see, there is a considerable amount of flexibility and power
provided by QueryFilter objects.  They are slightly harder to use and slightly
less efficient than the traditional node-path-only MUSCLE queries, but they
are there for you to use if you wish, or to ignore if you prefer not to use them.

</BODY>
</HTML>
